<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas demo-01</title>
  <style type="text/css">
    canvas {
      background: #ccc;
      display: block;
      margin: 0 auto;
    }

    #controls {
      width: 260px;
      height: 100%;
      padding: 10px;
      position: absolute;
      left: 0;
      top: 0;
      background: linear-gradient(to bottom, #000000, #b8b8b8);
      user-select: none;
    }

    #controls section {
      margin-top: 10px;
      height: 20px;
    }

    #controls .label {
      width: 50%;
      height: 20px;
      line-height: 20px;
      text-align: center;
      color: #FFF;
      display: block;
      float: left;
    }

    #xing {
      float: right;
      width: 50%;
      height: 20px;
    }

    #controls .color {
      width: 50%;
      height: auto;
      float: right;
    }

    #colors input {
      float: right;
      width: 48%;
      height: 20px;
      border: none;
    }

    #widths input {
      float: right;
      width: 49%;
      height: 20px;
      border: none;
    }

    #style {
      float: right;
      width: 49%;
      height: 20px;
      border: none;
    }

    input[type=button] {
      width: 150px;
      height: 30px;
      background: #C40000;
      color: #FFF;
      border-radius: 5px;
      margin-top: 10px;
      margin-left: 10px;
      border: none;
      display: block;
    }
  </style>
</head>

<body>
  <canvas id="canvas" width="500" height="500" style="border: 1px solid black;"></canvas>
  <div id="controls">
    <section id="shape">
      <label for="shape" class="label">选择形状 : </label>
      <select style="width: 120px;" id="xing">
        <option value="rect">矩形</option>
        <option value="line">直线</option>
        <option value="circle">内切圆</option>
        <option value="circle1">外接圆</option>
        <option value="circle2">中心圆</option>
        <option value="poly">多边形</option>
        <option value="pen">铅笔</option>
        <option value="eraser">橡皮</option>
        <option value="arrow">箭头</option>
      </select>
    </section>

    <section id="colors">
      <label for="color" class="label">选择颜色 : </label>
      <input style="width: 120px;" type="color" id="color">
    </section>
    <section id="widths">
      <label for="color" class="label">选择线宽 : </label>
      <input style="width: 120px;" type="number" id="width" value="2" step="1" min="2" max="20">
    </section>
    <section id="styles">
      <label for="shape" class="label">选择方式 : </label>
      <select style="width: 120px;" id="style">
        <option value="stroke">描边</option>
        <option value="fill">填充</option>
      </select>
    </section>
    <section id="sides">
      <label for="side" class="label">选择边数 : </label>
      <input style="width: 120px;" type="number" id="side" value="3" min="3" max="20">
    </section>
    <input type="button" value="撤销" id="redo">
    <input type="button" value="保存" id="save">
    <input type="button" value="清空" id="qingkong">
  </div>
  <script>
    var canvas = document.querySelector("canvas");
    var ctx = canvas.getContext("2d");
    var shape = document.querySelector("#xing");
    var color = document.querySelector("#color");
    var width = document.querySelector("#width");
    var style = document.querySelector("#style");
    var side = document.querySelector("#side");
    var redo = document.querySelector("#redo");
    var save = document.querySelector("#save");
    var qingkong = document.querySelector("#qingkong");
    // 记录历史绘制的图形
    var data = [];
    // shape
    var s = "rect";
    shape.onchange = function () {
      s = this.value;
    };
    // color
    var c = "#000";
    color.onchange = function () {
      c = this.value;
    };
    // stoke width
    var w = "2";
    width.onchange = function () {
      w = this.value;
    };
    // stoke style  stoke or fill
    var st = "stroke";
    style.onchange = function () {
      st = this.value;
    };
    // shap side size
    var sd = "3";
    side.onchange = function () {
      sd = this.value;
    };

    // 监听canvas的鼠标事件
    canvas.onmousedown = function (e) {
      var ox = e.offsetX;
      var oy = e.offsetY;
      var draw = new Draw(ctx, {
        color: c,
        width: w,
        style: st,
        side: sd
      });
      if (s == "pen") {
        ctx.beginPath();
        ctx.moveTo(ox, oy);
      }
      canvas.onmousemove = function (e) {
        var mx = e.offsetX;
        var my = e.offsetY;
        if (s != "eraser") {
          ctx.clearRect(0, 0, 500, 500);
          if (data.length != 0) {
            ctx.putImageData(data[data.length - 1], 0, 0, 0, 0, 500, 500); //将某个图像数据放置到画布指定的位置上  后面四个参数可省略
          }
        }
        draw[s](ox, oy, mx, my, sd);
      };
      document.onmouseup = function () {
        data.push(ctx.getImageData(0, 0, 500, 500)); //获取画布当中指定区域当中所有的图形数据
        canvas.onmousemove = null;
        document.onmouseup = null;
      }
    };
    
    // action of redo
    redo.onclick = function () {
      if (data.length == 0) {
        alert("无效操作");
        return;
      }
      ctx.clearRect(0, 0, 500, 500);
      data.pop();
      if (data.length == 0) {
        return;
      }
      ctx.putImageData(data[data.length - 1], 0, 0, 0, 0, 500, 500);
    };

    // action of save
    save.onclick = function () {
      var r = canvas.toDataURL();
      location.assign(r)
    };
    qingkong.onclick = function () {
      ctx.clearRect(0, 0, 500, 500);
      data = [];
    }
    class Draw {
      constructor(ctx, option) {
        this.ctx = ctx;
        this.color = option.color;
        this.width = option.width;
        this.style = option.style;
      }
      init() { //初始化
        this.ctx.strokeStyle = this.color;
        this.ctx.fillStyle = this.color;
        this.ctx.lineWidth = this.width;
      }
      rect(ox, oy, mx, my) {
        this.init();
        this.ctx.beginPath();
        // console.log('rect', mx - ox, my - oy)
        this.ctx.rect(ox, oy, mx - ox, my - oy);
        this.ctx[this.style]();
      }
      line(ox, oy, mx, my) {
        this.init();
        this.ctx.beginPath();
        this.ctx.moveTo(ox, oy);
        this.ctx.lineTo(mx, my);
        this.ctx.stroke();
      }
      circle(ox, oy, mx, my) { //内切圆
        this.init();
        this.ctx.beginPath();
        var r = Math.abs(mx - ox) > Math.abs(my - oy) ? Math.abs(my - oy) / 2 : Math.abs(mx - ox) / 2;
        this.ctx.arc(ox + (ox < mx ? r : -r), oy + (oy < my ? r : -r), r, 0, 2 * Math.PI);
        this.ctx[this.style]();
      }
      circle1(ox, oy, mx, my) { //外接圆
        this.init();
        this.ctx.beginPath();
        var r = Math.sqrt(Math.pow(mx - ox, 2) + Math.pow(my - oy, 2)) / 2;
        this.ctx.arc(ox + (mx - ox) / 2, oy + (my - oy) / 2, r, 0, 2 * Math.PI);
        this.ctx[this.style]();
      }
      circle2(ox, oy, mx, my) { //中心圆
        this.init();
        this.ctx.beginPath();
        var r = Math.sqrt(Math.pow(mx - ox, 2) + Math.pow(my - oy, 2));
        this.ctx.arc(ox, oy, r, 0, 2 * Math.PI);
        this.ctx[this.style]();
      }
      poly(ox, oy, mx, my, sd) {
        this.init();
        this.ctx.save();
        ctx.translate(ox, oy);
        this.ctx.rotate(Math.PI / 2);
        var angle = Math.PI / sd;
        var r = Math.sqrt(Math.pow(mx - ox, 2) + Math.pow(my - oy, 2));
        var x = Math.cos(angle) * r;
        var y = Math.sin(angle) * r;
        this.ctx.beginPath();
        this.ctx.moveTo(x, y);
        for (var i = 0; i < sd; i++) {
          this.ctx.lineTo(x, -y);
          this.ctx.rotate(-angle * 2)
        }
        this.ctx[this.style]();
        this.ctx.restore()
      }
      pen(ox, oy, mx, my) {
        this.init();
        this.ctx.lineTo(mx, my);
        this.ctx.stroke();
      }
      eraser(ox, oy, mx, my) {
        this.ctx.clearRect(mx, my, 10, 10);
      }
      arrow(ox, oy, mx, my) {//箭头工具
        this.init();
        beginPoint.x = ox;
        beginPoint.y = oy;
        stopPoint.x = mx;
        stopPoint.y = my;
        Plot.arrowCoord(beginPoint, stopPoint);
        Plot.sideCoord();
        Plot.drawArrow();
      }

    }

    var beginPoint = {};//画箭头需要的开始坐标
    var stopPoint = {};//画箭头需要的结束坐标
    var polygonVertex = [];//箭头多边形顶点的坐标
    var CONST = {//箭头参数
      edgeLen: 50,
      angle: 25
    };

    //封装的作图箭头工具对象
    var Plot = {
      angle: "",

      //在CONST中定义的edgeLen以及angle参数
      //短距离画箭头的时候会出现箭头头部过大，修改：
      dynArrowSize: function () {
        var x = stopPoint.x - beginPoint.x,
          y = stopPoint.y - beginPoint.y,
          length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        if (length < 250) {
          CONST.edgeLen = CONST.edgeLen / 2;
          CONST.angle = CONST.angle / 2;
        }
        else if (length < 500) {
          CONST.edgeLen = CONST.edgeLen * length / 500;
          CONST.angle = CONST.angle * length / 500;
        }
        // console.log(length);
      },

      //getRadian 返回以起点与X轴之间的夹角角度值
      getRadian: function (beginPoint, stopPoint) {
        Plot.angle = Math.atan2(stopPoint.y - beginPoint.y, stopPoint.x - beginPoint.x) / Math.PI * 180;
        console.log('Plot.angle', Plot.angle)
        paraDef(50, 40);
        Plot.dynArrowSize();
      },

      ///获得箭头底边两个点
      arrowCoord: function (beginPoint, stopPoint) {
        polygonVertex[0] = beginPoint.x;
        polygonVertex[1] = beginPoint.y;
        polygonVertex[6] = stopPoint.x;
        polygonVertex[7] = stopPoint.y;
        Plot.getRadian(beginPoint, stopPoint);
        polygonVertex[8] = stopPoint.x - CONST.edgeLen * Math.cos(Math.PI / 180 * (Plot.angle + CONST.angle));
        polygonVertex[9] = stopPoint.y - CONST.edgeLen * Math.sin(Math.PI / 180 * (Plot.angle + CONST.angle));
        polygonVertex[4] = stopPoint.x - CONST.edgeLen * Math.cos(Math.PI / 180 * (Plot.angle - CONST.angle));
        polygonVertex[5] = stopPoint.y - CONST.edgeLen * Math.sin(Math.PI / 180 * (Plot.angle - CONST.angle));
      },

      //获取另两个底边侧面点
      sideCoord: function () {
        var midpoint = {};
        // midpoint.x = polygonVertex[6] - (CONST.edgeLen * Math.cos(Plot.angle * Math.PI / 180));
        // midpoint.y = polygonVertex[7] - (CONST.edgeLen * Math.sin(Plot.angle * Math.PI / 180));
        midpoint.x = (polygonVertex[4] + polygonVertex[8]) / 2;
        midpoint.y = (polygonVertex[5] + polygonVertex[9]) / 2;
        polygonVertex[2] = (polygonVertex[4] + midpoint.x) / 2;
        polygonVertex[3] = (polygonVertex[5] + midpoint.y) / 2;
        polygonVertex[10] = (polygonVertex[8] + midpoint.x) / 2;
        polygonVertex[11] = (polygonVertex[9] + midpoint.y) / 2;
      },

      //画箭头
      drawArrow: function () {
        ctx.beginPath();
        ctx.moveTo(polygonVertex[0], polygonVertex[1]);
        ctx.lineTo(polygonVertex[2], polygonVertex[3]);
        ctx.lineTo(polygonVertex[4], polygonVertex[5]);
        ctx.lineTo(polygonVertex[6], polygonVertex[7]);
        ctx.lineTo(polygonVertex[8], polygonVertex[9]);
        ctx.lineTo(polygonVertex[10], polygonVertex[11]);
        // ctx.lineTo(polygonVertex[0], polygonVertex[1]);
        ctx.closePath();
        ctx.fill();
      }
    };

    //自定义参数
    function paraDef(edgeLen, angle) {
      CONST.edgeLen = edgeLen;
      CONST.angle = angle;
    }

  </script>
</body>

</html>